using System;
using System.Collections;
using System.Reflection;
using Velocity4Net.Runtime.Logging;
using Velocity4Net.Runtime.Parse.AST;
namespace Velocity4Net.Util.Introspection
{
    /**
     *  Implementation of Uberspect to provide the default introspective
     *  functionality of Velocity
     *
     * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
     * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
     * @version $Id: UberspectImpl.java 898032 2010-01-11 19:51:03Z nbubna $
     */
    public class UberspectImpl : Uberspect, UberspectLoggable
    {
        /**
         *  Our runtime logger.
         */
        protected Log log;

        /**
         *  the default Velocity introspector
         */
        protected Introspector introspector;

        /**
         *  init - generates the Introspector. As the setup code
         *  makes sure that the log gets set before this is called,
         *  we can initialize the Introspector using the log object.
         */
        public void init()
        {
            introspector = new Introspector(log);
        }

        /**
         *  Sets the runtime logger - this must be called before anything
         *  else.
         *
         * @param log The logger instance to use.
         * @since 1.5
         */
        public Log Log
        {
            set
            {
                this.log = value;
            }
        }


        /**
         *  To support iterative objects used in a <code>#foreach()</code>
         *  loop.
         *
         * @param obj The iterative object.
         * @param i Info about the object's location.
         * @return An {@link Iterator} object.
         */
        public IEnumerable getIterator(Object obj, Info i)
        {
            if (obj.GetType().IsArray)
            {
                return (Array)obj;
            }
            else if (obj is ICollection)
            {
                return ((ICollection)obj);
            }
            else if (obj is IDictionary)
            {
                return ((IDictionary)obj).Values;
            }
            else if (obj is IEnumerable)
            {
                if (log.isDebugEnabled())
                {
                    log.debug("The iterative object in the #foreach() loop at " +
                               i + " is of type java.util.Iterator.  Because " +
                               "it is not resettable, if used in more than once it " +
                               "may lead to unexpected results.");
                }
                return ((IEnumerable)obj);
            }
            /*  we have no clue what this is  */
            log.debug("Could not determine type of iterator in #foreach loop at " + i);

            return null;
        }

        /**
         *  Method
         * @param obj
         * @param methodName
         * @param args
         * @param i
         * @return A Velocity Method.
         */
        public VelMethod getMethod(Object obj, String methodName, Object[] args, Info i)
        {
            if (obj == null)
            {
                return null;
            }

            MethodInfo m = introspector.getMethod(obj.GetType(), methodName, args);
            if (m != null)
            {
                return new VelMethodImpl(m);
            }

            return null;
        }

        /**
         * Property  getter
         * @param obj
         * @param identifier
         * @param i
         * @return A Velocity Getter Method.
         * @throws Exception
         */
        public VelPropertyGet getPropertyGet(Object obj, String identifier, Info i)
        {
            if (obj == null)
            {
                return null;
            }

            Type claz = obj.GetType();

            /*
             *  first try for a getFoo() type of property
             *  (also getfoo() )
             */
            AbstractExecutor executor = new PropertyExecutor(log, introspector, claz, identifier);

            /*
             * Let's see if we are a map...
             */
            if (!executor.isAlive())
            {
                executor = new MapGetExecutor(log, claz, identifier);
            }

            /*
             *  if that didn't work, look for get("foo")
             */

            if (!executor.isAlive())
            {
                executor = new GetExecutor(log, introspector, claz, identifier);
            }

            return (executor.isAlive()) ? new VelGetterImpl(executor) : null;
        }

        /**
         * Property setter
         * @param obj
         * @param identifier
         * @param arg
         * @param i
         * @return A Velocity Setter method.
         * @throws Exception
         */
        public VelPropertySet getPropertySet(Object obj, String identifier,
                                             Object arg, Info i)
        {
            if (obj == null)
            {
                return null;
            }

            Type claz = obj.GetType();

            /*
             *  first try for a setFoo() type of property
             *  (also setfoo() )
             */
            SetExecutor executor = new SetPropertyExecutor(log, introspector, claz, identifier, arg);

            /*
             * Let's see if we are a map...
             */
            if (!executor.isAlive())
            {
                executor = new MapSetExecutor(log, claz, identifier);
            }

            /*
             *  if that didn't work, look for put("foo", arg)
             */

            if (!executor.isAlive())
            {
                executor = new PutExecutor(log, introspector, claz, arg, identifier);
            }

            return (executor.isAlive()) ? new VelSetterImpl(executor) : null;
        }

        /**
         *  Implementation of VelMethod
         */
        public class VelMethodImpl : VelMethod
        {
            MethodInfo method;
            Boolean isVarArg;
            bool wrapArray;

            /**
             * @param m
             */
            public VelMethodImpl(MethodInfo m)
                : this(m, false)
            {
            }

            /**
             * @since 1.6
             */
            public VelMethodImpl(MethodInfo method, bool wrapArray)
            {
                this.method = method;
                this.wrapArray = wrapArray;
            }

            private VelMethodImpl()
            {
                method = null;
            }

            /**
             * @see VelMethod#invoke(java.lang.Object, java.lang.Object[])
             */
            public Object invoke(Object o, Object[] actual)
            {
                // if we're pretending an array is a list...
                //if (wrapArray)
                //{
                //    o = new ArrayListWrapper(o);
                //}

                if (IsVarArg())
                {
                    ParameterInfo[] formal = method.GetParameters();
                    int index = formal.Length - 1;
                    if (actual.Length >= index)
                    {
                        Type type = formal[index].ParameterType;
                        actual = handleVarArg(type, index, actual);
                    }
                }

                // call extension point invocation
                return doInvoke(o, actual);
            }

            /**
             * Offers an extension point for subclasses (in alternate Uberspects)
             * to alter the invocation after any array wrapping or varargs handling
             * has already been completed.
             * @since 1.6
             */
            protected Object doInvoke(Object o, Object[] actual)
            {
                return method.Invoke(o, actual);
            }

            /**
             * @return true if this method can accept a variable number of arguments
             * @since 1.6
             */
            public bool IsVarArg()
            {
                if (isVarArg == null)
                {
                    ParameterInfo[] formal = method.GetParameters();
                    if (formal == null || formal.Length == 0)
                    {
                        this.isVarArg = false;
                    }
                    else
                    {
                        Type last = formal[formal.Length - 1].ParameterType;
                        // if the last arg is an array, then
                        // we consider this a varargs method
                        this.isVarArg = last.IsArray;
                    }
                }
                return isVarArg;
            }

            /**
             * @param type The vararg class type (aka component type
             *             of the expected array arg)
             * @param index The index of the vararg in the method declaration
             *              (This will always be one less than the number of
             *               expected arguments.)
             * @param actual The actual parameters being passed to this method
             * @returns The actual parameters adjusted for the varargs in order
             *          to fit the method declaration.
             */
            private Object[] handleVarArg(Type type,
                                           int index,
                                          Object[] actual)
            {
                // if no values are being passed into the vararg
                if (actual.Length == index)
                {
                    // copy existing args to new array
                    Object[] newActual = new Object[actual.Length + 1];
                    Array.Copy(actual, 0, newActual, 0, actual.Length);
                    // create an empty array of the expected type
                    newActual[index] = Array.CreateInstance(type, 0);
                    actual = newActual;
                }
                // if one value is being passed into the vararg
                else if (actual.Length == index + 1 && actual[index] != null)
                {
                    // make sure the last arg is an array of the expected type
                    Type argClass = actual[index].GetType();
                    if (!argClass.IsArray)
                    {
                        // create a 1-length array to hold and replace the last param
                        Array lastActual = Array.CreateInstance(type, 1);
                        lastActual.SetValue(actual[index], 0);
                        actual[index] = lastActual;
                    }
                }
                // if multiple values are being passed into the vararg
                else if (actual.Length > index + 1)
                {
                    // put the last and extra actual in an array of the expected type
                    int size = actual.Length - index;
                    Array lastActual = Array.CreateInstance(type, size);
                    for (int i = 0; i < size; i++)
                    {
                        lastActual.SetValue(actual[index + i], i);
                    }

                    // put all into a new actual array of the appropriate size
                    Object[] newActual = new Object[index + 1];
                    for (int i = 0; i < index; i++)
                    {
                        newActual[i] = actual[i];
                    }
                    newActual[index] = lastActual;

                    // replace the old actual array
                    actual = newActual;
                }
                return actual;
            }

            /**
             * @see org.apache.velocity.util.introspection.VelMethod#isCacheable()
             */
            public bool isCacheable()
            {
                return true;
            }

            /**
             * @see org.apache.velocity.util.introspection.VelMethod#getMethodName()
             */
            public String getMethodName()
            {
                return method.Name;
            }

            /**
             * @see org.apache.velocity.util.introspection.VelMethod#getReturnType()
             */
            public Type getReturnType()
            {
                return method.ReturnType;
            }
        }

        /**
         *
         *
         */
        public class VelGetterImpl : VelPropertyGet
        {
            AbstractExecutor getExecutor;

            /**
             * @param exec
             */
            public VelGetterImpl(AbstractExecutor exec)
            {
                getExecutor = exec;
            }

            private VelGetterImpl()
            {
                getExecutor = null;
            }

            /**
             * @see org.apache.velocity.util.introspection.VelPropertyGet#invoke(java.lang.Object)
             */
            public Object invoke(Object o)
            {
                return getExecutor.Execute(o);
            }

            /**
             * @see org.apache.velocity.util.introspection.VelPropertyGet#isCacheable()
             */
            public bool isCacheable()
            {
                return true;
            }

            /**
             * @see org.apache.velocity.util.introspection.VelPropertyGet#getMethodName()
             */
            public String getMethodName()
            {
                return getExecutor.isAlive() ? getExecutor.getMethod().Name : null;
            }
        }

        /**
         *
         */
        public class VelSetterImpl : VelPropertySet
        {
            private SetExecutor setExecutor;

            /**
             * @param setExecutor
             */
            public VelSetterImpl(SetExecutor setExecutor)
            {
                this.setExecutor = setExecutor;
            }

            private VelSetterImpl()
            {
                setExecutor = null;
            }

            /**
             * Invoke the found Set Executor.
             *
             * @param o is the Object to invoke it on.
             * @param value in the Value to set.
             * @return The resulting Object.
             * @throws Exception
             */
            public Object invoke(Object o, Object value)
            {
                return setExecutor.Execute(o, value);
            }

            /**
             * @see org.apache.velocity.util.introspection.VelPropertySet#isCacheable()
             */
            public bool isCacheable()
            {
                return true;
            }

            /**
             * @see org.apache.velocity.util.introspection.VelPropertySet#getMethodName()
             */
            public String getMethodName()
            {
                return setExecutor.isAlive() ? setExecutor.Method.Name : null;
            }
        }
    }
}